import json
from machine import PWM, Pin, ADC
import time
from time import sleep
from wifi_manager import WiFiManager
from web_file_manager import WebFileManager
import ulogger


def get_loggers(config: dict):
    class RealClock(ulogger.BaseClock):
        def __call__(self) -> str:
            timestamp = time.mktime(time.localtime())
            timestamp += 8 * 3600 # 加上 8 小时（北京时间）
            local_time = time.localtime(timestamp)
            return f'{local_time[1]:02d}-{local_time[2]:02d} {local_time[3]:02d}:{local_time[4]:02d}:{local_time[5]:02d}'
    clock  = RealClock()

    handlers = []

    if config['log']['terminal']['enable']:
        handlers.append(ulogger.Handler(
            level=ulogger.INFO,
            colorful=True,
            fmt="[&(level)%][&(time)%][&(name)%] &(msg)%",
            clock=clock,
            direction=ulogger.TO_TERM,
        ))

    if config['log']['file']['enable']:
        handlers.append(ulogger.Handler(
            level=ulogger.INFO,
            fmt="[&(level)%][&(time)%][&(name)%] &(msg)%",
            clock=clock,
            direction=ulogger.TO_FILE,
            file_name="night_lamp.log",
            max_file_size=config['log']['file']['size']
        ))

    if config['log']['udp']['enable']:
        handlers.append(ulogger.Handler(
            level=ulogger.INFO,
            fmt="[&(level)%][&(time)%][&(name)%] &(msg)%",
            clock=clock,
            direction=ulogger.TO_UDP,
            udp_target=(config['log']['udp']['target_ip'], config['log']['udp']['target_port'])
        ))


    return (ulogger.Logger('main', handlers), ulogger.Logger('wifi', handlers), ulogger.Logger('webfile', handlers))

def read_brightness(pin):
    raw_value = pin.read_u16()
    mapped_value = int((raw_value / 65535) * 100)
    return mapped_value

# LED 逐渐点亮
def led_smooth_on(led_mos_pwm: PWM, duration_s: float, min_duty = 0, max_duty = 65535):
    duty_step = (max_duty - min_duty) / (duration_s * 1000)

    duty = min_duty
    while True:
        ratio = duty / 65535
        led_mos_pwm.duty_u16((int)(65535 * ratio * ratio))
        # led_mos_pwm.duty_u16(int(duty))
        sleep(0.001)

        duty += duty_step
        if duty >= max_duty:
            break

    ratio = max_duty / 65535
    led_mos_pwm.duty_u16((int)(65535 * ratio * ratio))

# LED 逐渐熄灭
def led_smooth_off(led_mos_pwm: PWM, duration_s: float, min_duty = 0, max_duty = 65535):
    duty_step = (max_duty - min_duty) / (duration_s * 1000)

    duty = max_duty
    while True:
        ratio = duty / 65535
        led_mos_pwm.duty_u16((int)(65535 * ratio * ratio))
        # led_mos_pwm.duty_u16(int(duty))
        sleep(0.001)

        duty -= duty_step
        if duty <= min_duty:
            break

    ratio = min_duty / 65535
    led_mos_pwm.duty_u16((int)(65535 * ratio * ratio))

# LED 带有仪式感的启动
def led_breath(led_mos_pwm: PWM):
    led_smooth_off(led_mos_pwm, 1.5, min_duty = 10000)
    sleep(0.3)
    led_smooth_on(led_mos_pwm, 1.5, min_duty = 10000)
    led_smooth_off(led_mos_pwm, 0.3)

def wait_for_infrared_cool_down(logger: ulogger.Logger, infrared_pin: Pin, lock_down_duration: float):
    logger.info(f'INFRARED cooling down...')
    while infrared_pin.value() == 1: 
        sleep(0.1)
    logger.info(f'INFRARED cooled down, sleeping for lock down...')
    time.sleep(lock_down_duration) # 由于红外探头有封锁时间。保险起见。此处至少睡够 lock_down_duration 秒钟，等红外探头恢复到未激活状态
    logger.info(f'Finished lock down.')

def light_on_routine(logger: ulogger.Logger, config: dict, infrared_pin: Pin, brightness_pin: ADC, radar_pin: Pin, led_mos_pwm: Pin):
    logger.info('>>> ENTER >>> light_on_routine() >>>>>')
    enter_time = time.ticks_ms() / 1000

    led_smooth_on(led_mos_pwm, config['led']['smooth_on_duration'])

    while True: 
        brightness = read_brightness(brightness_pin)
        # print(f'Current brightness: {brightness}')
        if brightness >= config['sensors']['brightness']['off_brightness_threshold']:
            logger.info(f"BRIGHTNESS ({brightness} >= {config['sensors']['brightness']['off_brightness_threshold']})")
            break

        if radar_pin.value() == 0:
            logger.info(f"RADAR (-)")
            break

        sleep(0.1)

    quit_time = time.ticks_ms() / 1000
    logger.info(f'<<< QUIT <<< light_on_routine(). ({quit_time - enter_time:.2f} seconds)')
    led_smooth_off(led_mos_pwm, config['led']['smooth_off_duration'])



def main():
    try:
        with open('config.json') as f:
            config = json.load(f)

        main_logger, wifi_logger, webfile_logger = get_loggers(config)
        main_logger.info("<<< Night Lamp Project >>> Starting...")

        wifi_manager = WiFiManager(
            config['wifi']['ssid'], config['wifi']['password'], 
            as_hostname=config['wifi']['as_hostname'],
            state_led_pin_no=config['wifi']['state_led_pin_no'], 
            logger=wifi_logger)
        wifi_manager.enable()

        web_file_manager = WebFileManager(logger = webfile_logger)
        web_file_manager.start_file_server()

        infrared_pin = Pin(config['sensors']['infrared']['pin_no'], Pin.IN, Pin.PULL_DOWN)
        main_logger.info(f'Infrared pin no: {config['sensors']['infrared']['pin_no']}')

        brightness_pin = ADC(Pin(config['sensors']['brightness']['pin_no']))
        main_logger.info(f'Brightness pin no: {config['sensors']['brightness']['pin_no']}')

        radar_pin = Pin(config['sensors']['radar']['pin_no'], Pin.IN, Pin.PULL_DOWN)
        main_logger.info(f'RADAR pin no: {config['sensors']['radar']['pin_no']}')

        led_mos_pwm = PWM(Pin(config['led']['mosfet_pin_no'], Pin.OUT), freq=1000)
        main_logger.info(f'LED MOSFET pin no: {config['led']['mosfet_pin_no']}')

        main_logger.info('Take a deep breath...')
        led_breath(led_mos_pwm)

        while True:
            if infrared_pin.value() == 1:
                main_logger.info()
                main_logger.info(f"INFRARED (+)")

                if radar_pin.value() == 1:
                    main_logger.info(f"RADAR (+)")
                else:
                    radar_signal_detect_duration = None
                    start_time = time.ticks_ms()
                    while True:
                        if radar_pin.value() == 1:
                            radar_signal_detect_duration = (time.ticks_ms() - start_time) / 1000
                            break

                        if time.ticks_ms() - start_time > 1000:
                            break

                    if radar_signal_detect_duration is not None:
                        main_logger.warn(f"RADAR (+), {radar_signal_detect_duration:.2f} seconds DELAY")
                    else:
                        main_logger.warn(f"RADAR (-), INFRARED FALSE triggered???")
                        wait_for_infrared_cool_down(main_logger, infrared_pin, config['sensors']['infrared']['lock_down_duration'])
                        continue

                brightness = read_brightness(brightness_pin)
                if brightness <= config['sensors']['brightness']['on_brightness_threshold']:
                    main_logger.info(f'BRIGHTNESS ({brightness} <= {config['sensors']['brightness']['on_brightness_threshold']}), light up!')
                    light_on_routine(main_logger, config, infrared_pin, brightness_pin, radar_pin, led_mos_pwm)
                    wait_for_infrared_cool_down(main_logger, infrared_pin, config['sensors']['infrared']['lock_down_duration'])
                else:
                    main_logger.info(f'BRIGHTNESS (({brightness} > {config['sensors']['brightness']['on_brightness_threshold']}), do nothing')
                    wait_for_infrared_cool_down(main_logger, infrared_pin, config['sensors']['infrared']['lock_down_duration'])
                    continue
            else:
                sleep(0.05)
    except KeyboardInterrupt:
        main_logger.info("KeyboardInterrupt")
        web_file_manager.stop_file_server()
        wifi_manager.disable()
        
        if led_mos_pwm.duty_u16() != 0:
            led_smooth_off(led_mos_pwm, config['led']['smooth_off_duration'])
    
if __name__ == '__main__':
    main()